<?php

/*
 * This file is part of PHP ENTITY LINK.
 *
 *  PHP ENTITY LINK is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PHP ENTITY LINK is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with PHP ENTITY LINK.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Copyright (C) 2010 Peter Jacques, Michael Kyndt
 *
 */

namespace php_entity_link{

    use \Exception;

    require_once ("DS.class.php");
    require_once ("Entity.class.php");


    /**
     * Translates object to queries and querie resultsets to objects. One instance
     * of this class is responisble for one table.
     *
     * @author Peter Jacques, Michael Kyndt
     * @package php entity link
     * @version 1.0.0
     */
    class EntityDS {

        /**
         * DS instance for the executing of queries
         * @var <DS>
         */
        protected $db;
        /**
         * Table name in de database
         * @var <string>
         */
        protected $table;
        /**
         * Fields in the table with the name, type, default value, ...
         * @var <type>
         */
        protected $fields;
        /**
         * Primary keys
         * @var <array>
         */
        protected $pk_fields;
        /**
         * place holder for the name of the autoincrement field
         * @var <string>
         */
        protected $auto_increment_field;

        /**
         * Constructor. Creates an instance for the management of one table for
         * the given database connection.
         * @param <string> $table table name
         * @param <string> $connection_name connection name defined in the hostfile.
         */
        public function __construct($table,$connection_name,$db=null){
            try{
                $this->table = $table;
                $this->db = DS::getInstance($connection_name,$db);
                $this->pk_fields = array();
                $this->auto_increment_field = null;
                $this->setFields();
                $this->fk_fields = array();
            }catch(Exception $ex){
                throw $ex;
            }
        }//__construct

        /**
         * Use this function to get the ds instance in you extended class.
         * @return <DS> the ds from this
         */
        protected function getDS(){
            return $this->db;
        }//getDS

        /**
         * Switches table at run time. Throws exception on failure
         * @param <string> $table new table name.
         * @param <type> $connection_name new connection, if null previous
         *  connection will be used.
         */
        public function switchTable($table,$connection_name=null){
            try{
                if($connection_name != null){
                    $this->db = DS::getInstance($connection_name);
                }
                if($this->table != $table){
                    $this->table = $table;
                    $this->pk_fields = array();
                    $this->auto_increment_field = null;
                    $this->setFields();
                    $this->fk_fields = array();
                }
            }catch(Exception $ex){
                throw $ex;
            }
        }//switchTable

        /**
         * Inits the fields array.
         */
        private function setFields() {
            try {
                $this->fields = $this->db->getFields($this->table);
                $has_pk = false; $i_count = count($this->fields);
                $keys = array_keys($this->fields); $i = 0;
                while($i < $i_count) {
                    $field = $this->fields[$keys[$i]];
                    if($field['Key'] == 'PRI') {
                        $this->pk_fields[$field['Field']] = $field['Field'];
                        $has_pk = true;
                    }
                    if(strstr($field['Extra'], "auto_increment")) {
                        $this->auto_increment_field = $field['Field'];
                    }
                    $i++;
                }//next i
            }catch(Exception  $ex){
                throw $ex;
            }
        }//getFields

        /**
         * Returns the simple php type mapped to the database type of the field.
         * @param <string> $field field name
         * @return <string> simple php type
         */
        private function getFieldMappedType($field){
            $field = $this->fields[$field];
            return $field['m_type'];
        }//getFieldMappedType

        /**
         * Creates a null record. A record with the null and/or default values
         * for a new record in de table.
         * @param <type> $use_default_values when true set de default values,
         *  when false set alle parameters to null
         * @return <Entity> the per definition empty object.
         */
        public function getNullObject($use_default_values=true){
            $params = array();
            foreach($this->fields as $key=>$value){
                $field = $value;
                if($use_default_values){
                    $param_value = ($field['Default'] == "")? NULL:$field['Default'];
                }else{
                    $param_value = NULL;
                }
                $params[$field['Field']] = $param_value;
            }
            $object = new Entity($this->table, $params, true);
            return $object;
        }//getNullObject



        /**
         * Select the first record of an select. Returns NULL or null object
         * when resultset is empty.
         * @param int $count Out parameters gives the number of elements complying
         * to the where statemetns
         * @param <string> $where The where statement
         * @param <string> $orderby The orderby statement
         * @param <boolean> $rnoe returns a null object on true (default) or NULL on false
         * @return <NULL/Entity> null or Entity instance
         */

        public function selectSingle(&$count,$where="",$orderby="",$null_object=true){
            $items = $this->select($count, $where, $orderby, 0, 1);
            if(count($items) > 0){
                return $items[0];
            }else{
                $count = 0;
                if($null_object){
                    return $this->getNullObject();
                }else{
                    return null;
                }
            }
        }//selectSingle

        /**
         * Queries the table for all the records acordingly to the where statement.
         * @param <int> $count Out parameter. Returns the number records found with
         *  the given where statement, discarding the page and limit parameters.
         *  Throws an exception on error.
         * @param <string> $where The where statement
         * @param <string> $orderby The orderby statement
         * @param <int> $page Use in combination wiht limit. To browse through records
         *  for paging.
         * @param <int> $limit The maximum number of records to be returend
         * @return <array> Array of Entities. Empty array when no records found.
         */
        public function select(&$count, $where="",$orderby="", $page = 0, $limit = 0){
            $query = "select * from ".$this->table;
            $query_count = "select count(*) as number from ".$this->table;

            if($where != null && $where != ""){
                $query .= " where ".$where;
                $query_count .= " where ".$where;
            }
            if($orderby != null && $orderby != ""){
                    $query .= " order by ".$orderby;
            }
            if($limit > 0){
                    $start = $page * $limit;
                    $query .= " limit ".$start.",".$limit;
            }

            //exec select
            try{
                $pk_fields = $this->pk_fields;
                $rows = $this->db->select($query);
                $records = array();
                foreach($rows as $row){
                    $record = new Entity($this->table, $row, true);
                    array_push($records, $record);
                }//next row

                if($limit > 0) {
                    $rows = $this->db->select($query_count);
                    foreach($rows as $row){
                        $total_records_found = $row['number'];
                    }//next row
                    $count = $total_records_found;
                }else {
                    $count = count($records);
                }

                return $records;
            }catch(Exception $ex){
                throw $ex;
            }
        }//select

        /**
         * Insterts an Entity record in the tabel. Throws exception on failure.
         * @param <Entity> $object The entity record to be inserted.
         */
        private function insert(Entity $object){
            $pk_fields = $this->pk_fields;

            //build query
            $fields = ""; $values = "";
            $params = $object->getParams();
            foreach($params as $key=>$value){
                $fields .= ",".$key;
                $values .= ",".$this->getSQLValueString($value, $this->getFieldMappedType($key));
            }//next key=>value
            $fields = substr($fields, 1);
            $values = substr($values, 1);

            $query = "insert into ".$object->getTable()." (".$fields.")values(".$values.")";

            //exec insert query
            try{
                $id = $this->db->insert($query);
                if($id > 0 && $id != "" && $this->auto_increment_field != null){
                    $ai_field_name = $this->auto_increment_field;
                    $object->$ai_field_name = $id;
                }
            }catch(Exception $ex){
                throw $ex;
            }
        }//insert

        /**
         * Updates an record in the table. Throws exception on failure.
         * @param <Entity> $object The entity record to be updated.
         */
        private function update(Entity $object){
            //build update query
            $set = "";
            $params = $object->getParams();
            foreach($params as $key=>$value){
                //if(!in_array($key, $this->pk_fields)){
                    $set_part = ", %s = %s";
                    $set .= sprintf($set_part, $key ,$this->getSQLValueString($value, $this->getFieldMappedType($key)));
                //}
            }//next key=>value
            $set = substr($set, 1);

            $query = "update ".$object->getTable()." set ".$set;
            $query .= " where ".$this->getPk_where($object);

            //exec update query
            try{
                $this->db->update($query);
            }catch(Exception $ex){
                throw $ex;
            }
        }//update

        /**
         * Insters or updates an entity record in the table. Throws exception on
         *   failure.
         * @param Entity $object The entity record to be insterted or updated.
         */
        public function save(Entity $object){
            if($object->getTable() != $this->table){
                throw new Exception("Can't save Entity object. Object of table '".$this->table."' expected");
            }
            if(empty($this->pk_fields)){
                throw new Exception("Can't save Entity object. No primary key value found");
            }

            //if record with pk in database update else insert
            $pk_where = $this->getPk_where($object);
            $records = $this->select($count, $pk_where);
            $insert = count($records) <= 0;

            if($insert){
                $this->insert($object);
            }else{
                $this->update($object);
            }
        }//save

        /**
         * Deletes an entity object form the table. Throws exception on failure.
         * @param <Entity> $object The entity record to be deleted.
         */
        public function delete(Entity $object){
            if($object->getTable() != $this->table){
                throw new Exception("Can't delete Entity object. Object of table '".$this->table."' expected");
            }
            //if($this->pk_field == ""){
            if(empty($this->pk_fields)){
                throw new Exception("Can't save Entity object. No primary key value found");
            }

            //build query
            $query = "delete from ".$object->getTable();
            $query .= " where ".$this->getPk_where($object);
            $teller = 0;

            try{
                $this->db->delete($query);
            }catch(Exception $ex){
                throw $ex;
            }
        }//delete

        /**
         * Creates a where statement using the primary key fields
         * @param <Entity> $object entity record
         * @return <string> where statement
         */
        private function getPk_where($object){
            $pk_where = ""; $teller = 0;
            foreach ($this->pk_fields as $var => $val){
                $value = $object->$val;
                if ($teller != count($this->pk_fields) -1){
                    $pk_where.= $var." = ".$this->getSQLValueString($value, $this->getFieldMappedType($val))." AND ";
                }else{
                    $pk_where .= $var." = ".$this->getSQLValueString($value, $this->getFieldMappedType($val));
                }
                $teller ++;
            }//next key=>val
            return $pk_where;
        }//getPk_where

        /**
         * Use this function to escape an sql parameter.
         * Throws an exception on failure.
         * @param <type> $theValue the value to escape
         * @param <string> $theType the simple php type. DS::DB_TYPE_STRING, ...
         * @return <type> the escapes value
         */
        public function getSQLValueString($theValue, $theType) {
            return $this->db->getSQLValueString($theValue, $theType);
        }//getSQLValueString

    }//EntityDS

}//php_entity_link

?>